Option Explicit

Implements ICsvReader

'#######################################
' 使用方法
' Dim lineReader As LineCsvReader
' Set lineReader = new LineCsvReader
' lineReader.init filepath, exceptColNames
'
' Dim reader As ICsvReader
' Set reader = lineReader
' Dim data As Variant
' Do While Not reader.isEOF
'     data = reader.readNext
' Loop
'
'#######################################

'#######################################
' Field
'#######################################
Private fileSystem As Object
' 文件流对象
Private fileStream As Object
' 文件大小
Private fileSize As Long
' 文件路径
Private usingFilepath As String
' 已经读取的文件大小
Private readSize As Long
' 需要使用的列的索引数组
Private colIndexes As Variant
' colIndexes 中的最大索引
Private maxColIndex As Integer

'#######################################
' Constructor
'#######################################
Private Sub Class_Initialize()
    Set fileSystem = CreateObject("Scripting.FileSystemObject")
End Sub

Private Sub Class_Terminate()
    If Not fileStream Is Nothing Then
        fileStream.Close
        Set fileStream = Nothing
    End If

    Set fileSystem = Nothing
End Sub

'#######################################
' Method
'#######################################
' @param filepath 文件路径
' @param exceptColNames 需要使用的索引名
Public Sub InitByTitleLine(ByRef filepath As String, ByRef exceptColNames Variant, Optional ByRef titleLineNo as Long=1, Optional ByRef startLineNo as Long=2)
On Error GoTo catch
    Dim fileInfo As Object
    Set fileInfo = fileSystem.getfile(filepath)
    fileSize = fileInfo.Size
    Set fileInfo = Nothing

    Set fileStream = fileSystem.OpenTextFile(filepath)

catch:
    Set fileInfo = Nothing
End Sub



'---------------------------------------------------------------
'---------------------------------------------------------------
' bk
'---------------------------------------------------------------
'---------------------------------------------------------------
'#######################################
' 初始化
'#######################################
' @param 文件路径
' @param 需要使用的列名列表
Public Sub Init(ByRef filepath As String, ByRef exceptColNames As Variant)
On Error GoTo catch
    ' 0 获取文件对象
    Dim fileInfo As Object
    Set fileInfo = fileSystem.getfile(filepath)
    fileSize = fileInfo.Size
    Set fileInfo = Nothing

    ' 1. 打开文件
    Set fileStream = fileSystem.OpenTextFile(filepath)
    usingFilepath = filepath

    ' 2. 从第一行的列名数据中，确认各个列名的列索引
    ' 2.1 如果文件中已经没有数据则，则立刻停止
    If fileStream.AtEndOfLine Then
        Exit Sub
    End If

    ' 2.2 如果第一行是空行，则抛出异常
    Dim firstLine As String
    firstLine = fileStream.Readline
    pushLineSchdule firstLine
    firstLine = Trim(firstLine)

    If firstLine = "" Then
        Err.Raise 1102, Description:= "LineCsvReader: file[" & filepath & "] first line is empty. can not read file"
    End If

    ' 2.3 确认各个列名的列索引
    determinColIndex Split(firstLine, ","), exceptColNames

    ' 3. 舍弃第二行的逻辑名称
    If fileStream.AtEndOfLine Then
        Exit Sub
    End If
    pushLineSchdule fileStream.Readline

    Exit Sub
catch:
    Err.Raise 1101, Description:= "LineCsvReader: can not read file[" & filepath & "]."

    Set fileInfo = Nothing
End Sub

'#######################################
' ICsvReader Function
'#######################################
' 读取下一行数据
Public Function ICsvReader_readNext() As Variant
    Dim result() As String
    ReDim result(0 To maxColIndex)

    ' 1. 读取下一行数据
    Dim lineStr As String
    lineStr = fileStream.Readline
    pushLineSchdule lineStr
    lineStr = Trim(lineStr)

    ' 2. 如果这一行是空行，则返回空数据
    If lineStr = "" Then
        Exit Function
    End If

    ' 3. 获取各个列的数据
    Dim colDatas As Variant
    colDatas = Split(lineStr, ",")

    Dim maxColDataIndex As Integer
    maxColDataIndex = UBound(colDatas)

    Dim i As Integer
    For i = 0 To maxColIndex
        If colIndexes(i) < maxColDataIndex Then
            result(i) = colDatas(colIndexes(i))
        End If
    Next

    ICsvReader_readNext = result
End Function

' 确认文件是否已经读取结束
Public Function ICsvReader_isEOF() As Boolean
    If fileStream Is Nothing Or fileStream.AtEndOfLine Then
        ICsvReader_isEOF = True
    Else
        ICsvReader_isEOF = False
    End If
End Function

'#######################################
' private Function
'#######################################
' 确认各个列名的列索引
' @param realColNames 文件中的真实列名(数组)
' @param exceptColNames 需要使用的列名(数组)
Private Sub determinColIndex(ByRef realColNames As Variant, ByRef exceptColNames As Variant)
On Error GoTo catch
    ' 1. 将真实列名转换成 map
    Dim realColNameIndexMap As Object
    Set realColNameIndexMap = UtilsArray.arrayToValAndIndexMap(realColNames)

    maxColIndex = UBound(exceptColNames)

    Dim result() As Integer
    ReDim result(0 To maxColIndex)

    ' 2. 确认各个列名的列索引
    '    如果某个列不存在，则抛出异常
    Dim i As Integer
    For i = 0 To maxColIndex
        If realColNameIndexMap.Exists(exceptColNames(i)) Then
            result(i) = realColNameIndexMap.Item(exceptColNames(i))
        Else
            Err.Raise 1104, Description:="LineCsvReader: can not find column[" & exceptColNames(i) & "]"
        End If
    Next

    colIndexes = result
    Exit Sub
catch:
    Err.Raise 1104, Description:="LineCsvReader: can not determin column Index" & Chr(10) & Chr(10) & Err.Number & ": " & Err.Description
End Sub

' 刷新文件读取状态到进度条
Private Sub pushLineSchdule(ByRef lineStr As String)
    readSize = readSize + Len(lineStr)
    StateManager.refresh readSize / fileSize * 100, "reading file [" & usingFilepath & "]" & Chr(10) & "size: " & readSize & "/" & fileSize & Chr(10) & "RowNo: " & fileStream.Line
End Sub